// GNU (L)GPLv3+ here.

#include "MaxsiGameEngine.h"
#include <X11/X.h>
#include <X11/Xlib.h>
#include <GL/gl.h>
#include <GL/glx.h>
#include <GL/glu.h>

extern bool ShouldMainLoop;

BeginMaxsiNamespace

Display*				dpy;
Window					root;
GLint					att[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None };
XVisualInfo*			vi;
Colormap				cmap;
XSetWindowAttributes	swa;
Window					win;
GLXContext				glc;
XWindowAttributes		gwa;
XWindowAttributes		wa;
XEvent					xev;

float	RotationVelocity_X	=	0.0;
float	RotationVelocity_Y	=	50.0;
float	RotationVelocity_Z	=	50.0;
GLfloat	RotationMatrix[16];

MaxsiError CreateWindow()
{
	dpy = XOpenDisplay(NULL);
 
	if( dpy == NULL ) { printf("\n\tcannot connect to X server\n\n"); return 1; }
        
	root = DefaultRootWindow(dpy);

	vi = glXChooseVisual(dpy, 0, att);

	if ( vi == NULL )
	{
		printf("\n\tno appropriate visual found\n\n");
		return 1;
	}

	cmap = XCreateColormap(dpy, root, vi->visual, AllocNone);

	swa.colormap = cmap;
	swa.event_mask = ExposureMask | KeyPressMask;
 
	win = XCreateWindow(dpy, root, 0, 0, 800, 600, 0, vi->depth, InputOutput, vi->visual, CWColormap | CWEventMask, &swa);

	XMapWindow(dpy, win);
	XStoreName(dpy, win, "Maxsi Game Engine");

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError DeleteWindow()
{
	XDestroyWindow(dpy, win);
	XCloseDisplay(dpy);

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError Create3DContext()
{
	char		font_string[128];
	XFontStruct	*font_struct;

	// Create GL context and make it current.
	if ((glc = glXCreateContext(dpy, vi, NULL, GL_TRUE)) == NULL) { printf("\n\tcannot create gl context\n\n");	return MAXSI_ERROR_UNSPECIFIED; }

	glXMakeCurrent(dpy, win, glc);
	glEnable(GL_DEPTH_TEST);
	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	//glClearColor(0.00, 0.00, 0.40, 1.00);

	// Find a font.
	for (int font_size = 14; font_size < 32; font_size += 1)
	{
		//sprintf(font_string, "-adobe-times-*-r-normal--%i-*", font_size);
		sprintf(font_string, "-adobe-courier-*-r-normal--%i-*", font_size);
		font_struct = XLoadQueryFont(dpy, font_string);
	
		if ( font_struct != NULL )
		{
	 		glXUseXFont(font_struct->fid, 32, 192, 32);		
			break;
		}
	}

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glGetFloatv(GL_MODELVIEW_MATRIX, RotationMatrix);

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError Delete3DContext()
{
	glXMakeCurrent(dpy, None, NULL);
	glXDestroyContext(dpy, glc);

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError DrawBox(float size, Vec3 Location, Vec3 Rotation)
{
	//printf("Drawing box!\n");

	//float RotationMatrix[16];

	//for ( size_t I = 0; I < 16; I++ ) { RotationMatrix[I] = 0; }

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	//glRotatef(Rotation.X, 1.0f, 0.0f, 0.0f);
	glRotatef(Rotation.Y, 0.0f, 1.0f, 0.0f);
	glRotatef(Rotation.Z, 0.0f, 0.0f, 1.0f);
	glMultMatrixf(RotationMatrix);
	glGetFloatv(GL_MODELVIEW_MATRIX, RotationMatrix);
#if 0
#endif

	glBegin(GL_QUADS);

	float Legend = 1.0f;
	float Dary = 1.0f;

	glColor3f(Legend, Dary, Dary);

	glVertex3f(-size, -size, -size);
	glVertex3f( size, -size, -size);
	glVertex3f( size,  size, -size);
	glVertex3f(-size,  size, -size);

	glVertex3f(-size, -size,  size);
	glVertex3f( size, -size,  size);
	glVertex3f( size,  size,  size);
	glVertex3f(-size,  size,  size);

	glColor3f(Dary, Dary, Legend);

	glVertex3f(-size, -size, -size);
	glVertex3f(-size, -size,  size);
	glVertex3f(-size,  size,  size);
	glVertex3f(-size,  size, -size);

	glVertex3f( size, -size, -size);
	glVertex3f( size, -size,  size);
	glVertex3f( size,  size,  size);
	glVertex3f( size,  size, -size);

	glColor3f(Dary, Legend, Dary);

	glVertex3f(-size, -size, -size);
	glVertex3f(-size, -size,  size);
	glVertex3f( size, -size,  size);
	glVertex3f( size, -size, -size);

	glVertex3f(-size, size, -size);
	glVertex3f(-size, size,  size);
	glVertex3f( size, size,  size);
	glVertex3f( size, size, -size);

	glEnd();

	return MAXSI_ERROR_SUCCESS;
}

void DrawCube(float size)
{
	glBegin(GL_QUADS);

	float Legend = 1.0f;
	float WaitForIt = 0.4f;
	float Dary = 0.2f;

	glColor3f(Legend, WaitForIt, Dary);

	glVertex3f(-size, -size, -size);
	glVertex3f( size, -size, -size);
	glVertex3f( size,  size, -size);
	glVertex3f(-size,  size, -size);

	glVertex3f(-size, -size,  size);
	glVertex3f( size, -size,  size);
	glVertex3f( size,  size,  size);
	glVertex3f(-size,  size,  size);

	glColor3f(Dary, WaitForIt, Legend);

	glVertex3f(-size, -size, -size);
	glVertex3f(-size, -size,  size);
	glVertex3f(-size,  size,  size);
	glVertex3f(-size,  size, -size);

	glVertex3f( size, -size, -size);
	glVertex3f( size, -size,  size);
	glVertex3f( size,  size,  size);
	glVertex3f( size,  size, -size);

	glColor3f(WaitForIt, Legend, Dary);

	glVertex3f(-size, -size, -size);
	glVertex3f(-size, -size,  size);
	glVertex3f( size, -size,  size);
	glVertex3f( size, -size, -size);

	glVertex3f(-size, size, -size);
	glVertex3f(-size, size,  size);
	glVertex3f( size, size,  size);
	glVertex3f( size, size, -size);

	glEnd();
}

void RotateCube()
{
	float DeltaTime = GetRenderDeltaTime();

	//DeltaTime = 0.000000f;

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	//glRotatef(RotationVelocity_X * DeltaTime, 1.0f, 0.0f, 0.0f);
	glRotatef(RotationVelocity_Y * DeltaTime, 0.0f, 1.0f, 0.0f);
	glRotatef(RotationVelocity_Z * DeltaTime, 0.0f, 0.0f, 1.0f);
	glMultMatrixf(RotationMatrix);
	glGetFloatv(GL_MODELVIEW_MATRIX, RotationMatrix);
}

void CheckKeyboard()
{
	float DeltaTime = GetRenderDeltaTime();

	if ( XCheckWindowEvent(dpy, win, KeyPressMask, &xev) )
	{
		char*	key_string	=	XKeysymToString(XKeycodeToKeysym(dpy, xev.xkey.keycode, 0));

		if (strncmp(key_string, "Left", 4) == 0)	{ RotationVelocity_Z -= 400.0f*DeltaTime; } else
		if (strncmp(key_string, "Right", 5) == 0)	{ RotationVelocity_Z += 400.0f*DeltaTime; } else
		if (strncmp(key_string, "Up", 2) == 0)		{ RotationVelocity_Y -= 400.0f*DeltaTime; } else
		if (strncmp(key_string, "Down", 4) == 0)	{ RotationVelocity_Y += 400.0f*DeltaTime; } else
		if (strncmp(key_string, "F1", 2) == 0)		{ RotationVelocity_Y  = 0.0f; RotationVelocity_Z = 0.0f; } else
		if (strncmp(key_string, "Escape", 5) == 0)	{ ShouldMainLoop = false; }
	}
}

MaxsiError OnBegunRenderingScene()
{
	RotateCube();

	// Calculate the aspect ratio.
	XGetWindowAttributes(dpy, win, &wa);
	glViewport(0, 0, wa.width, wa.height);

 	float	AspectRatio		=	(float)(wa.width) / (float)(wa.height);

	// Set up projection and modelview.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(-2.50f * AspectRatio, 2.50f * AspectRatio, -2.50f, 2.50f, 1.0f, 100.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(10.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 1.f);
	glMultMatrixf(RotationMatrix);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

#if 0
	// Draw the cube.
	Vec3 Location;
	Vec3 Rotation;

	Location.X = 0.0f;
	Location.Y = 0.0f;
	Location.Z = 0.0f;
	Rotation.X = GetRenderGameTime()*0.0001f;
	Rotation.Y = GetRenderGameTime()*0.0001f;
	Rotation.Z = GetRenderGameTime()*0.0001f;
	//Rotation.X = GetRenderDeltaTime()*0.0001f;
	//Rotation.Y = GetRenderDeltaTime()*0.0001f;
	//Rotation.Z = GetRenderDeltaTime()*0.0001f;
	
	DrawBox(1.0, Location, Rotation);
#else

	//DrawCube(1.0f);
	float X = GetRenderGameTime() * 1.0f;

	// Y = M / (1 + e^(-A*X))
	for ( size_t I = 0; I < 500; I++)
	{
		DrawCube( 1.0f / ( 1.0f + pow(2.718281828, 3.0f + -1.0f * X) ) );
	}

	//DrawCube( cos(1.0f/(X*X)) );

	//DrawCube(log(fabs(5.0-GetRenderGameTime()*0.3)));

#endif

	return MAXSI_ERROR_SUCCESS;
}

MaxsiError OnFinishedRenderingScene()
{
	// Display time, fps etc.
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, (float)wa.width, 0, (float)wa.height, -1.0f, 1.0f);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glColor3f(0.0f, 0.0f, 0.0f);

	char info_string[256];

	sprintf(info_string, "Logic %4.1f fps / Render %4.1f fps @ %ix%ipx (Frame L%zu/R%zu)", GetLogicFPS(), GetRenderFPS(), wa.width, wa.height, GetLogicFrameNum(), GetRenderFrameNum());
	glRasterPos2i(10, 10);
	glCallLists(strlen(info_string), GL_UNSIGNED_BYTE, info_string);

	sprintf(info_string, "<up, down, left, right> rotate cube * <F1> stop rotation ");
	glRasterPos2i(10, wa.height-32);
	glCallLists(strlen(info_string), GL_UNSIGNED_BYTE, info_string);

	// Oh joy! The scene has successfully been rendered!
	glXSwapBuffers(dpy, win);

	return MAXSI_ERROR_SUCCESS;	
}

EndMaxsiNamespace
